////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek Studios, 2001-2010.
// -------------------------------------------------------------------------
//  File name:   Statoscope.cpp
// -------------------------------------------------------------------------
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "Statoscope.h"
#include "FrameProfileSystem.h"
#include <IGame.h>
#include <IGameFramework.h>
#include <IAudioDevice.h>
#include <IRenderer.h>
#include <ICryAnimation.h>
#include "SimpleStringPool.h"
#include "System.h"
#include "MemReplay.h"







#if ENABLE_STATOSCOPE

namespace 
{
	// use a dummy function to forward to vfprintf(since in release fprintf is defined to be emtpy)
	// but un-defing the empty define breaks on ps3, since there the f* functions are already overloaded
	// and i will break with the filehandlecache
	int OutputLine(void *vfp, const char *format, ... )
	{
		FILE* fp = reinterpret_cast<FILE*>(vfp);

		va_list args;
		va_start(args, format);
		int ret = vfprintf(fp,format, args);
		va_end(args);
		return ret;
		}
	}

struct SFrameLengthDG : public SDataGroup
{
	SFrameLengthDG() : SDataGroup('f', "frame lengths", "['/' (float frameLengthInMS)]", 1) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		fr.AddValue(gEnv->pTimer->GetRealFrameTime() * 1000.0f);
	}
};

struct SGraphicsDG : public SDataGroup
{
	SGraphicsDG() : SDataGroup('g', "graphics", "['/Graphics/' (float GPUUsageInPercent) (float GPUFrameLengthInMS) (int numTris) (int numDrawCalls)"
																							"(int numShadowDrawCalls) (int numGeneralDrawCalls) (int numTransparentDrawCalls) (int numTotalDrawCalls)"
																							"(int numDrawCallsRejectedByConditionalRendering)"
																							"(int numPostEffects) (int numForwardLights) (int numForwardShadowCastingLights)]", 12) 
	{
		m_gpuUsageHistory.reserve(GPU_HISTORY_LENGTH);
		m_frameTimeHistory.reserve(GPU_HISTORY_LENGTH);

		m_lastFrameScreenShotRequested=0;
		m_totFrameTime=0.f;

		ResetGPUUsageHistory();
	
		REGISTER_CVAR2("e_StatoscopeScreenCapWhenGPULimited", &m_cvarScreenCapWhenGPULimited, 0, VF_NULL, "Statoscope will take a screen capture when we are GPU limited");
	}

	void ResetGPUUsageHistory()
	{
		m_gpuUsageHistory.clear();
		m_frameTimeHistory.clear();

		for(uint32 i=0; i<GPU_HISTORY_LENGTH; i++)
		{
			m_gpuUsageHistory.push_back(0.f);
			m_frameTimeHistory.push_back(0.f);
		}
		m_nFramesGPULmited = 0;
		m_totFrameTime = 0;
	}

	void TrackGPUUsage(float gpuLoad, float frameTimeMs, int totalDPs)
	{
		float oldGPULoad = m_gpuUsageHistory[0];

		if(oldGPULoad >= 99.f)
		{
			m_nFramesGPULmited--;
		}
		if(gpuLoad >= 99.f)
		{
			m_nFramesGPULmited++;
		}

		m_gpuUsageHistory.erase(m_gpuUsageHistory.begin());
		m_gpuUsageHistory.push_back(gpuLoad);

		float oldFrameTime = m_frameTimeHistory[0];

		m_totFrameTime -= oldFrameTime;
		m_totFrameTime += frameTimeMs;

		m_frameTimeHistory.erase(m_frameTimeHistory.begin());
		m_frameTimeHistory.push_back(frameTimeMs);

		int currentFrame = gEnv->pRenderer->GetFrameID(false);

		//Don't spam screen shots
		bool bScreenShotAvaliable = (currentFrame >= (m_lastFrameScreenShotRequested+SCREEN_SHOT_FREQ));

		bool bFrameTimeToHigh = ((m_totFrameTime / (float)GPU_HISTORY_LENGTH) > (float)GPU_TIME_THRESHOLD_MS);




		bool bGpuLimited = (m_nFramesGPULmited==GPU_HISTORY_LENGTH);


		bool bDPLimited = (totalDPs>DP_THRESHOLD);

		if( bScreenShotAvaliable && (bDPLimited || (bGpuLimited && bFrameTimeToHigh)) )
		{
			if(bDPLimited)
			{
				string userMarkerName;
				userMarkerName.Format("DP Limit exceeded (%d): Screen Shot", totalDPs);
				gEnv->pStatoscope->AddUserMarker(userMarkerName);
				//printf("[Statoscope] DP Limited: %d, requesting ScreenShot frame: %d\n", totalDPs, currentFrame);
			}
			else
			{
				gEnv->pStatoscope->AddUserMarker("GPU Limited: Screen Shot");
				//printf("[Statoscope] GPU Limited, requesting ScreenShot frame: %d\n", currentFrame);
			}

			gEnv->pStatoscope->RequestScreenShot();
			m_lastFrameScreenShotRequested = currentFrame;
		}

	}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		IRenderer::SRenderTimes renderTimes;
		gEnv->pRenderer->GetRenderTimes(renderTimes);

		float GPUUsageInPercent = 100.f - renderTimes.fTimeGPUIdlePercent;
		fr.AddValue(GPUUsageInPercent);
		fr.AddValue(GPUUsageInPercent * 0.01f * gEnv->pTimer->GetRealFrameTime() * 1000.0f);

		int numTris, numShadowVolPolys;
		gEnv->pRenderer->GetPolyCount(numTris, numShadowVolPolys);
		fr.AddValue(numTris);

		int numDrawCalls, numShadowDrawCalls, numGeneralDrawCalls, numTransparentDrawCalls;
		gEnv->pRenderer->GetCurrentNumberOfDrawCalls(numDrawCalls, numShadowDrawCalls);
		numGeneralDrawCalls = gEnv->pRenderer->GetCurrentNumberOfDrawCalls( 1 << EFSLIST_GENERAL );
		numTransparentDrawCalls = gEnv->pRenderer->GetCurrentNumberOfDrawCalls( 1 << EFSLIST_TRANSP );
		fr.AddValue(numDrawCalls);
		fr.AddValue(numShadowDrawCalls);
		fr.AddValue(numGeneralDrawCalls);
		fr.AddValue(numTransparentDrawCalls);
		fr.AddValue(numDrawCalls + numShadowDrawCalls);



		fr.AddValue(0);

		fr.AddValue(*((int*)gEnv->pRenderer->EF_Query(EFQ_NumActivePostEffects)));

		PodArray<CDLight*> *pLights = gEnv->p3DEngine->GetDynamicLightSources();
		uint32 nDynamicLights = pLights->Count();
		uint32 nShadowCastingLights = 0;

		// for some stupid reason nDynamicLights can be huge (like 806595888)
		if (nDynamicLights > 100)
		{
			nDynamicLights = 0;
		}

		for (uint32 i=0; i<nDynamicLights; i++)
		{
			CDLight *pLight = pLights->GetAt(i);

			if (pLight->m_Flags & DLF_CASTSHADOW_MAPS)
			{
				nShadowCastingLights++;
			}
		}

		fr.AddValue(nDynamicLights);
		fr.AddValue(nShadowCastingLights);

		if(m_cvarScreenCapWhenGPULimited)
		{
			TrackGPUUsage(GPUUsageInPercent, gEnv->pTimer->GetRealFrameTime() * 1000.0f, numDrawCalls + numShadowDrawCalls );
		}
	}

	static const int GPU_HISTORY_LENGTH = 10;
	static const int SCREEN_SHOT_FREQ = 60;
	static const int GPU_TIME_THRESHOLD_MS = 40;
	static const int DP_THRESHOLD = 2200;

	std::vector<float> m_gpuUsageHistory;
	std::vector<float> m_frameTimeHistory;
	int m_nFramesGPULmited;
	float m_totFrameTime;
	int m_lastFrameScreenShotRequested;
	int m_cvarScreenCapWhenGPULimited;
};

struct SEF_ListDG : public SDataGroup
{
	//These MUST match the EFSLIST_ lists from IShader.h
	SEF_ListDG() : SDataGroup('e', "ef_lists", "['/EF_Lists/' "
		"(float timeInvalid) "
		"(float timePreProcess) "
		"(float timeGeneral) "
		"(float timeTerrainLayer) "
		"(float timeShadowGen) "
		"(float timeDecal) "
		"(float timeWaterVolumes) "
		"(float timeTransparent) "
		"(float timeWater) "
		"(float timeHDRPostProcess) "
		"(float timeAfterHDRPostProcess) "
		"(float timePostProcess) "
		"(float timeAfterPostProcess) "
		"(float timeShadowPass) "
		"(float timeRefractPass) "
		"(float timeDeferredPostProcess) "
		"(float sumTimeEFList) "
		"]", 17) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		float fPassTime, fTotalTime = 0.0f;
		for(int i = 0; i < EFSLIST_NUM; i++)
		{
			fPassTime = gEnv->pRenderer->GetCurrentDrawCallRTTimes( 1 << i );
			fr.AddValue(fPassTime * 1000.0f);
			fTotalTime += fPassTime;
		}
		fr.AddValue(fTotalTime * 1000.0f);
	}
};

struct SMemoryDG : public SDataGroup
{
	SMemoryDG() : SDataGroup('m', "memory", "['/Memory/' (int mainMemUsageInMB) (int vidMemUsageInMB)]", 2) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		IMemoryManager::SProcessMemInfo processMemInfo;
		GetISystem()->GetIMemoryManager()->GetProcessMemInfo(processMemInfo);
		fr.AddValue((int)(processMemInfo.PagefileUsage/(1024.f * 1024.f)));

		//vidmem not reporting valid stats on 360, despite the func being v.expensive



		size_t vidMem, lastVidMem;
		gEnv->pRenderer->GetVideoMemoryUsageStats(vidMem, lastVidMem);
		fr.AddValue((uint32)vidMem);

	}
};

struct SStreamingDG : public SDataGroup
{
	SStreamingDG() : SDataGroup('s', "streaming", "['/Streaming/' (float cgfStreamingMemUsedInMB) (float cgfStreamingMemRequiredInMB)]", 2) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		I3DEngine::SObjectsStreamingStatus objectsStreamingStatus;
		gEnv->p3DEngine->GetObjectsStreamingStatus(objectsStreamingStatus);
		fr.AddValue(objectsStreamingStatus.nAllocatedBytes / (1024.f * 1024.f));
		fr.AddValue(objectsStreamingStatus.nMemRequired / (1024.f * 1024.f));
	}
};

struct SAIStatsDG : public SDataGroup
{
	SAIStatsDG() : SDataGroup('n', "ai stats", "['/AI/' (int enabledActors) (int fullUpdates) (int tpsSyncQueries) (int tpsASyncQueries)]", 4) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		IAISystem::AIStats stats;
		gEnv->pAISystem->GetStats(stats);

		fr.AddValue(stats.enabledActors);
		fr.AddValue(stats.fullUpdates);
		fr.AddValue(stats.tpsQueriesSync);
		fr.AddValue(stats.tpsQueriesASync);
	}
};

struct SStreamingAudioDG : public SDataGroup
{
	SStreamingAudioDG() : SDataGroup('a', "streaming audio", "['/StreamingAudio/' (float bandwidthActual MB/s) (float bandwidthRequested MB/s)]", 2) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		I3DEngine::SStremaingBandwidthData subsystemStreamingData;
		memset(&subsystemStreamingData, 0, sizeof(I3DEngine::SStremaingBandwidthData));
		gEnv->p3DEngine->GetStreamingSubsystemData(eStreamTaskTypeSound, subsystemStreamingData);
		fr.AddValue(subsystemStreamingData.fBandwidthActual);
		fr.AddValue(subsystemStreamingData.fBandwidthRequested);
	}
};

struct SStreamingTexturesDG : public SDataGroup
{
	SStreamingTexturesDG() : SDataGroup('x', "streaming textures", "['/StreamingTextures/' (float bandwidthActual MB/s) (float bandwidthRequested MB/s) (float numUpdated Ups/s) (float numRequested Req/s) (float numRendered UpsRen/s) (float poolMemUsed MB) (float poolMemWanted MB)]", 7) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		I3DEngine::SStatisticData textureData;
		I3DEngine::SStremaingBandwidthData subsystemStreamingData;

		gEnv->p3DEngine->GetStreamingSubsystemData(eStreamTaskTypeTexture, subsystemStreamingData);
		gEnv->p3DEngine->GetStatisticsData(textureData);

		fr.AddValue(subsystemStreamingData.fBandwidthActual);
		fr.AddValue(subsystemStreamingData.fBandwidthRequested);
		fr.AddValue(textureData.textureUpdates);
		fr.AddValue(textureData.textureRequests);
		fr.AddValue(textureData.textureRenders);
		fr.AddValue(textureData.texturePoolMemUsage);
		fr.AddValue(textureData.texturePoolMemRequired);
	}
};

struct SStreamingObjectsDG : public SDataGroup
{
	SStreamingObjectsDG() : SDataGroup('o', "streaming objects", "['/StreamingObjects/' (float bandwidthActual MB/s) (float bandwidthRequested MB/s)]", 2) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		I3DEngine::SStremaingBandwidthData subsystemStreamingData;
		memset(&subsystemStreamingData, 0, sizeof(I3DEngine::SStremaingBandwidthData));
		gEnv->p3DEngine->GetStreamingSubsystemData(eStreamTaskTypeGeometry, subsystemStreamingData);
		fr.AddValue(subsystemStreamingData.fBandwidthActual);
		fr.AddValue(subsystemStreamingData.fBandwidthRequested);
	}
};

struct SThreadsDG : public SDataGroup
{
	SThreadsDG() : SDataGroup('t', "threading", "['/Threading/' (float MTLoadInMS) (float MTWaitingForRTInMS) "
																					 "(float RTLoadInMS) (float RTWaitingForMTInMS) (float RTWaitingForGPUInMS)"
																					 "(float RTFrameLengthInMS) (float RTSceneDrawningLengthInMS)"
#if !defined(PS3)
																					 "]", 7) {}


#endif

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		IRenderer::SRenderTimes renderTimes;
		gEnv->pRenderer->GetRenderTimes(renderTimes);

		float RTWaitingForMTInMS = renderTimes.fWaitForMain * 1000.f;
		float MTWaitingForRTInMS = renderTimes.fWaitForRender * 1000.f;
		float RTWaitingForGPUInMS = renderTimes.fWaitForGPU * 1000.f;
		float RTLoadInMS = renderTimes.fTimeProcessedRT * 1000.f;

		float MTLoadInMS = (gEnv->pTimer->GetRealFrameTime() * 1000.0f) - MTWaitingForRTInMS;

		//Load represents pure RT work, so compensate for GPU sync
		RTLoadInMS = RTLoadInMS - RTWaitingForGPUInMS;

		fr.AddValue(MTLoadInMS);
		fr.AddValue(MTWaitingForRTInMS);
		fr.AddValue(RTLoadInMS);
		fr.AddValue(RTWaitingForMTInMS);
		fr.AddValue(RTWaitingForGPUInMS);
		fr.AddValue(renderTimes.fTimeProcessedRT * 1000);
		fr.AddValue(renderTimes.fTimeProcessedRTScene * 1000);





















	}
};

struct SCPUTimesDG : public SDataGroup
{
	SCPUTimesDG() : SDataGroup('j', "CPU Times", "['/CPUTimes/' (float physTime) "
																														 "(float particleTime) (float particleSyncTime) (int particleNumEmitters) "
																														 "(float animTime) (int animNumCharacters) "
																														 "(float aiTime) "
																														 "(float flashTime)]", 8) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		uint32 gUpdateTimeIdx = 0, gUpdateTimesNum = 0;
		const sUpdateTimes *gUpdateTimes = gEnv->pSystem->GetUpdateTimeStats(gUpdateTimeIdx, gUpdateTimesNum);
		float curPhysTime = gEnv->pTimer->TicksToSeconds(gUpdateTimes[gUpdateTimeIdx].PhysStepTime)*1000;
		fr.AddValue(curPhysTime);

		IParticleManager *pPartMan = gEnv->p3DEngine->GetParticleManager();
		if (pPartMan != NULL)
		{
			uint32 nNumEmitter = pPartMan->NumEmitter();
			float fTimeMS = gEnv->pTimer->TicksToSeconds(pPartMan->NumFrameTicks())*1000;
			float fTimeSyncMS = gEnv->pTimer->TicksToSeconds(pPartMan->NumFrameSyncTicks())*1000;

			fr.AddValue(fTimeMS);
			fr.AddValue(fTimeSyncMS);
			fr.AddValue(nNumEmitter);
		}
		else
		{
			fr.AddValue(0.0f);
			fr.AddValue(0.0f);
			fr.AddValue(0);
		}
		
		ICharacterManager *pCharManager = gEnv->pCharacterManager;
		if (pCharManager != NULL)
		{
			uint32 nNumCharacters = pCharManager->NumCharacters();
			float fTimeMS = gEnv->pTimer->TicksToSeconds(pCharManager->NumFrameTicks())*1000;

			fr.AddValue(fTimeMS);
			fr.AddValue(nNumCharacters);
		}
		else
		{
			fr.AddValue(0.0f);
			fr.AddValue(0);
		}

		IAISystem *pAISystem = gEnv->pAISystem;
		if (pAISystem != NULL)
		{
			float fTimeMS = gEnv->pTimer->TicksToSeconds(pAISystem->NumFrameTicks())*1000;
			fr.AddValue(fTimeMS);
		}
		else
		{
			fr.AddValue(0.0f);
		}

		float flashCost = -1.0f;
		gEnv->pSystem->GetFlashProfileResults(flashCost, true);
		if (flashCost >= 0.0f)
		{
			float flashCostInMS = flashCost * 1000.0f;
			fr.AddValue(flashCostInMS);
		}
		else
		{
			fr.AddValue(0.0f);
		}
	}
};

struct SGPUTimesDG : public SDataGroup
{
	// note: if you change 'i' to something else, be sure to change NeedsGPUTimers() too
	SGPUTimesDG() : SDataGroup('i', "GPU Times", "['/GPUTimes/' (float Scene) (float Shadows) (float ZPass) "
																														 "(float DeferredDecals) (float DeferredLighting) "
																														 "(float Ambient) (float Cubemaps) (float SSAO+GI) "
																														 "(float Lights) (float Opaque) (float Transparent) "
																														 "(float HDR) (float PostFX)]", 13) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		IRenderer *pRenderer = gEnv->pRenderer;

		fr.AddValue(pRenderer->GetGPUTimer2("SCENE"));
		fr.AddValue(pRenderer->GetGPUTimer2("SHADOWMAPS") + pRenderer->GetGPUTimer2("SHADOWMASK"));
		fr.AddValue(pRenderer->GetGPUTimer2("ZPASS"));
		fr.AddValue(pRenderer->GetGPUTimer2("DEFERRED_DECALS"));
		fr.AddValue(pRenderer->GetGPUTimer2("DEFERRED_LIGHTING"));
		fr.AddValue(pRenderer->GetGPUTimer2("DL_AMBIENT"));
		fr.AddValue(pRenderer->GetGPUTimer2("DL_CUBEMAPS"));
		fr.AddValue(pRenderer->GetGPUTimer2("SSAO") + pRenderer->GetGPUTimer2("SSAO_APPLY") + pRenderer->GetGPUTimer2("GI"));
		fr.AddValue(pRenderer->GetGPUTimer2("DL_LIGHTS0") + pRenderer->GetGPUTimer2("DL_LIGHTS1"));
		fr.AddValue(pRenderer->GetGPUTimer2("OPAQUE")
#ifndef XENON
							- pRenderer->GetGPUTimer2("SHADOWMASK")  // Shadow mask updated during opaque pass
#endif
		);

		fr.AddValue(pRenderer->GetGPUTimer2("TRANSPARENT"));
		fr.AddValue(pRenderer->GetGPUTimer2("HDR"));
		fr.AddValue(pRenderer->GetGPUTimer2("POSTFX"));
	}
};

struct SVertexCostDG : public SDataGroup
{
	SVertexCostDG() : SDataGroup('v', "Vertex data",
																		"['/VertexData/' (int StaticPolyCountZ) (int SkinnedPolyCountZ) (int VegetationPolyCountZ)]", 3) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		IRenderer *pRenderer = gEnv->pRenderer;

		int32 nPolyCountZ =  pRenderer->GetPolygonCountByType(EFSLIST_GENERAL, EVCT_STATIC, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_SHADOW_GEN, EVCT_STATIC, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_TRANSP, EVCT_STATIC, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_DECAL, EVCT_STATIC, 1);
		fr.AddValue(nPolyCountZ);

		nPolyCountZ = pRenderer->GetPolygonCountByType(EFSLIST_GENERAL, EVCT_SKINNED, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_SHADOW_GEN, EVCT_SKINNED, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_TRANSP, EVCT_SKINNED, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_DECAL, EVCT_SKINNED, 1);
		fr.AddValue(nPolyCountZ);

		nPolyCountZ = pRenderer->GetPolygonCountByType(EFSLIST_GENERAL, EVCT_VEGETATION, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_SHADOW_GEN, EVCT_VEGETATION, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_TRANSP, EVCT_VEGETATION, 1);
		nPolyCountZ +=  pRenderer->GetPolygonCountByType(EFSLIST_DECAL, EVCT_VEGETATION, 1);
		fr.AddValue(nPolyCountZ);
	}
};

struct SParticlesDG : public SDataGroup
{
	SParticlesDG() : SDataGroup('p', "particles", "['/Particles/' (int numParticlesRendered) (int numParticlesActive) (int numParticlesAllocated) "
																								"(float particleScreenFractionRendered) (float particleScreenFractionProcessed) "
																								"(int numEmittersRendered) (int numEmittersActive) (int numEmittersAllocated) "
																								"(int numParticlesReiterated) (int numParticlesRejected) "
																								"(int numParticlesCollideTest) (int numParticlesCollideHit) (int numParticlesClipped)]", 13) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		SParticleCounts particleCounts;
		gEnv->pParticleManager->GetCounts(particleCounts);

		float fScreenPix = (float)(gEnv->pRenderer->GetWidth() * gEnv->pRenderer->GetHeight());
		fr.AddValue(particleCounts.ParticlesRendered); fr.AddValue(particleCounts.ParticlesActive); fr.AddValue(particleCounts.ParticlesAlloc); 
		fr.AddValue(particleCounts.PixelsRendered / fScreenPix); fr.AddValue(particleCounts.PixelsProcessed / fScreenPix); 
		fr.AddValue(particleCounts.EmittersRendered); fr.AddValue(particleCounts.EmittersActive); fr.AddValue(particleCounts.EmittersAlloc);
		fr.AddValue(particleCounts.ParticlesReiterate); fr.AddValue(particleCounts.ParticlesReject); 
		fr.AddValue(particleCounts.ParticlesCollideTest); fr.AddValue(particleCounts.ParticlesCollideHit); fr.AddValue(particleCounts.ParticlesClip);
	}
};

struct SLocationDG : public SDataGroup
{
	SLocationDG() : SDataGroup('l', "location", "['/' (float posx) (float posy) (float posz) (float rotx) (float roty) (float rotz)]", 6) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		Matrix33 m = Matrix33(gEnv->pRenderer->GetCamera().GetMatrix());
		Vec3 pos = gEnv->pRenderer->GetCamera().GetPosition();
		Ang3 rot = RAD2DEG(Ang3::GetAnglesXYZ(m));

		fr.AddValue(pos.x);
		fr.AddValue(pos.y);
		fr.AddValue(pos.z);
		
		fr.AddValue(rot.x);
		fr.AddValue(rot.y);
		fr.AddValue(rot.z);
	}
};

struct SPerCGFGPUProfilersDG : public SDataGroup
{
	CSimpleStringPool m_cattedCGFNames;

	SPerCGFGPUProfilersDG()
	// Frame IDs are currently problematic and will be ommitted 
	//: SDataGroup('c', "per-cgf gpu profilers", "['/DrawCalls/$' (int frameID) (int totalDrawCallCount) (int numInstances)]", 4)
	: SDataGroup('c', "per-cgf gpu profilers", "['/DrawCalls/$' (int totalDrawCallCount) (int numInstances)]", 3)
	, m_cattedCGFNames( true )
	{}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
#if defined(_RELEASE)
		CryFatalError( "Per-CGF GPU profilers not enabled in release" );
#else
		IRenderer* pRenderer = gEnv->pRenderer;

		pRenderer->CollectDrawCallsInfo(true);

		IRenderer::RNDrawcallsMapMesh& drawCallsInfo = gEnv->pRenderer->GetDrawCallsInfoPerMesh();

		IRenderer::RNDrawcallsMapMeshItor pEnd = drawCallsInfo.end();
		IRenderer::RNDrawcallsMapMeshItor pItor = drawCallsInfo.begin();

		string sPathName;
		sPathName.reserve(64);

		//Per RenderNode Stats
		for( ; pItor != pEnd; ++ pItor )
		{
			IRenderMesh *pRenderMesh = pItor->first;
			IRenderer::SDrawCallCountInfo &pInfo = pItor->second;

			uint32 nDrawcalls = pInfo.nShadows + pInfo.nZpass + pInfo.nGeneral + pInfo.nTransparent + pInfo.nMisc;

			const char* pRenderMeshName = pRenderMesh->GetSourceName();
			const char* pNameShort = strrchr(pRenderMeshName, '/');

			if(pNameShort)
			{
				pRenderMeshName = pNameShort+1;
			}

			if( pInfo.nShadows > 0 )
			{
				sPathName = "Shadow/";
				sPathName += pRenderMesh->GetTypeName();
				sPathName += "/";
				sPathName += pRenderMeshName;
				sPathName += "/";
				fr.AddValue( m_cattedCGFNames.Append( sPathName.c_str(), sPathName.length() ) );
				//fr.AddValue( batchStat->nFrameID );
				fr.AddValue( pInfo.nShadows );
				fr.AddValue( 1 );
				fr.m_drawProfilerRecordCount++;
			}
			if( pInfo.nGeneral > 0 )
			{
				sPathName = "Opaque/";
				sPathName += pRenderMesh->GetTypeName();
				sPathName += "/";
				sPathName += pRenderMeshName;
				sPathName += "/";
				fr.AddValue( m_cattedCGFNames.Append( sPathName.c_str(), sPathName.length() ) );
				//fr.AddValue( batchStat->nFrameID );
				fr.AddValue( pInfo.nGeneral );
				fr.AddValue( 1 );
				fr.m_drawProfilerRecordCount++;
			}
			if( pInfo.nTransparent > 0 )
			{
				sPathName = "Transparent/";
				sPathName += pRenderMesh->GetTypeName();
				sPathName += "/";
				sPathName += pRenderMeshName;
				sPathName += "/";
				fr.AddValue( m_cattedCGFNames.Append( sPathName.c_str(), sPathName.length() ) );
				//fr.AddValue( batchStat->nFrameID );
				fr.AddValue( pInfo.nTransparent );
				fr.AddValue( 1 );
				fr.m_drawProfilerRecordCount++;
			}
			if( pInfo.nZpass > 0 )
			{
				sPathName = "ZPass/";
				sPathName += pRenderMesh->GetTypeName();
				sPathName += "/";
				sPathName += pRenderMeshName;
				sPathName += "/";
				fr.AddValue( m_cattedCGFNames.Append( sPathName.c_str(), sPathName.length() ) );
				//fr.AddValue( batchStat->nFrameID );
				fr.AddValue( pInfo.nZpass );
				fr.AddValue( 1 );
				fr.m_drawProfilerRecordCount++;
			}
			if( pInfo.nMisc > 0 )
			{
				sPathName = "Misc/";
				sPathName += pRenderMesh->GetTypeName();
				sPathName += "/";
				sPathName += pRenderMeshName;
				sPathName += "/";
				fr.AddValue( m_cattedCGFNames.Append( sPathName.c_str(), sPathName.length() ) );
				//fr.AddValue( batchStat->nFrameID );
				fr.AddValue( pInfo.nMisc );
				fr.AddValue( 1 );
				fr.m_drawProfilerRecordCount++;
			}
		}

		//Flash
		{
			unsigned int numDPs, numTris;

			((CSystem*)gEnv->pSystem)->GetFlashRenderStats(numDPs, numTris);

			if(numDPs)
			{
				fr.AddValue( m_cattedCGFNames.Append( "Flash/Scaleform/All/", strlen( "Flash/Scaleform/All/" ) ) );
				//fr.AddValue( batchStat->nFrameID );
				fr.AddValue( numDPs );
				fr.AddValue( 1 );
				fr.m_drawProfilerRecordCount++;
			}
		}
#endif
	}

	virtual uint32 GetNumDataSets(const SPerfStatFrameRecord &fr)
	{
		return fr.m_drawProfilerRecordCount;
	}
};

struct SFrameProfilersDG : public SDataGroup
{
	SFrameProfilersDG() : SDataGroup('r', "frame profilers", "['/Threads/$' (int count) (float selfTimeInMS)]", 3) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		for (uint32 i=0; i<fr.m_frameProfilerRecords.size(); i++)
		{
			SPerfStatFrameProfilerRecord &fpr = fr.m_frameProfilerRecords[i];
			fr.AddValue(fpr.m_pProfiler);
			fr.AddValue(fpr.m_count);
			fr.AddValue(fpr.m_selfTime);
		}
	}

	virtual uint32 GetNumDataSets(const SPerfStatFrameRecord &fr)
	{
		return fr.m_frameProfilerRecords.size();
	}

	virtual string GetPath(void *p)
	{
		CFrameProfiler *pProfiler = (CFrameProfiler*)p;

		if (pProfiler)
		{
			const char *sThreadName = CryThreadGetName(pProfiler->m_threadId);
			char sThreadNameBuf[11]; // 0x 12345678 \0 => 2+8+1=11
			if (!sThreadName || !sThreadName[0])
			{
				_snprintf(sThreadNameBuf, sizeof(sThreadNameBuf), "0x%08x", pProfiler->m_threadId);
			}

			string path = sThreadName ? sThreadName : sThreadNameBuf;
			path += "/";
			path += gEnv->pFrameProfileSystem->GetModuleName(pProfiler);
			path += "/";
			path += pProfiler->m_name;
			path += "/";

			return path;
		}
		else
		{
			return string("SmallFunctions/SmallFunction/");
		}
	}
};

struct STexturePoolBucketsDG : public SDataGroup
{
	STexturePoolBucketsDG() : SDataGroup('b', "texture pool buckets", "['/TexturePool/$' (int size KB/s)]", 2) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		for (uint32 i=0; i<fr.m_texturePoolAllocationRecords.size(); i++)
		{
			SPerfTexturePoolAllocationRecord &fta = fr.m_texturePoolAllocationRecords[i];
			fr.AddValue(fta.m_path.c_str());
			fr.AddValue(fta.m_size);
		}
	}

	virtual uint32 GetNumDataSets(const SPerfStatFrameRecord &fr)
	{
		return fr.m_texturePoolAllocationRecords.size();
	}
};

struct SPerfCountersDG : public SDataGroup
{
	SPerfCountersDG() : SDataGroup('q', "performance counters", "['/PerfCounters/' (int lhsCount) (int iCacheMissCount)]", 2) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{







		fr.AddValue(0);
		fr.AddValue(0);

	}

	static void InitPM()
	{
















	}














	static void ShutdownPM()
	{




	}
};
struct SUserMarkerDG : public SDataGroup
{
	SUserMarkerDG() : SDataGroup('u', "user markers", "['/UserMarkers/' (string name)]", 1) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		for (uint32 i=0; i<fr.m_userMarkers.size(); i++)
		{
			fr.AddValue(fr.m_userMarkers[i].c_str());
		}
	}

	virtual uint32 GetNumDataSets(const SPerfStatFrameRecord &fr)
	{
		return fr.m_userMarkers.size();
	}
};

struct SCallstacksDG : public SDataGroup
{
	CSimpleStringPool m_callstackAddressStrings;

	SCallstacksDG() : SDataGroup('k', "callstacks", "['/Callstacks/$' (string callstack)]", 2),
		m_callstackAddressStrings(true) {}

	virtual void AddDataToFrameRecord(SPerfStatFrameRecord &fr)
	{
		for (uint32 i=0; i<fr.m_callstacks.size(); i++)
		{
			const SCallstack &callstack = fr.m_callstacks[i];
			uint32 numAddresses = callstack.m_addresses.size();
			string callstackString;
			callstackString.reserve((numAddresses * 11) + 1); // see ptrStr

			for (uint32 j=0; j<numAddresses; j++)
			{
				char ptrStr[12]; // 0x + 12345678 + " " + '\0'
				sprintf_s(ptrStr, sizeof(ptrStr), "0x%08x ", (uint32)callstack.m_addresses[j]);
				callstackString += ptrStr;
			}

			fr.AddValue(callstack.m_tag.c_str());
			fr.AddValue(m_callstackAddressStrings.Append(callstackString.c_str(), callstackString.length()));
		}
	}

	virtual uint32 GetNumDataSets(const SPerfStatFrameRecord &fr)
	{
		return fr.m_callstacks.size();
	}
};

const char* CStatoscope::GetDataGroupsCVarHelpString()
{
	const char *helpStr = "Which data groups are recorded each frame: flags+ enables, flags- disables\n"
												" 0 = none\n"
												" 1 = all\n";

	uint32 sizeNeeded = strlen(helpStr) + 1;	// +1 for the null terminator

	for (uint32 i=0; i<m_allDataGroups.size(); i++)
	{
		SDataGroup *dg = m_allDataGroups[i];
		sizeNeeded += strlen(" ");
		sizeNeeded += 1;
		sizeNeeded += strlen("+ = ");
		sizeNeeded += strlen(dg->m_name);
		sizeNeeded += strlen("\n");
	}

	static string helpString;
	helpString.reserve(sizeNeeded);

	helpString.append(helpStr);

	for (uint32 i=0; i<m_allDataGroups.size(); i++)
	{
		SDataGroup *dg = m_allDataGroups[i];
		helpString.append(" ");
		helpString.append(1, dg->m_id);
		helpString.append("+ = ");
		helpString.append(dg->m_name);
		helpString.append("\n");
	}

	return helpString.c_str();
}

CStatoscope::CStatoscope()
{
	m_lastDumpTime = 0.f;
	m_screenshotLastCaptureTime = 0.f;
	m_activeDataGroupMask = 0;
	m_groupMaskInitialized = false;

	m_pScreenShotBuffer = NULL;
	m_ScreenShotState = eSSCS_Idle;
	m_pScreenShotRSXOffset = 0;

	m_allDataGroups.push_back(new SUserMarkerDG());
	m_allDataGroups.push_back(new SCallstacksDG());
	m_allDataGroups.push_back(new SFrameLengthDG());
	m_allDataGroups.push_back(new SGraphicsDG());
	m_allDataGroups.push_back(new SEF_ListDG());
	m_allDataGroups.push_back(new SMemoryDG());
	m_allDataGroups.push_back(new SStreamingDG());
	m_allDataGroups.push_back(new SAIStatsDG());
	m_allDataGroups.push_back(new SThreadsDG());
	m_allDataGroups.push_back(new SCPUTimesDG());
	m_allDataGroups.push_back(new SGPUTimesDG());
	m_allDataGroups.push_back(new SVertexCostDG());
	m_allDataGroups.push_back(new SParticlesDG());
	m_allDataGroups.push_back(new SLocationDG());
	m_allDataGroups.push_back(new SPerCGFGPUProfilersDG());
	m_allDataGroups.push_back(new SFrameProfilersDG());
	m_allDataGroups.push_back(new SStreamingAudioDG());
	m_allDataGroups.push_back(new SStreamingTexturesDG());
	m_allDataGroups.push_back(new SStreamingObjectsDG());
	m_allDataGroups.push_back(new STexturePoolBucketsDG());
	m_allDataGroups.push_back(new SPerfCountersDG());

	m_pStatoscopeDumpPeriodCVar = REGISTER_FLOAT("e_StatoscopeDumpPeriod", 0.0f, VF_NULL, "How many seconds between Statoscope log writing (0 to disable).");
	m_pStatoscopeDumpManualCVar = REGISTER_INT("e_StatoscopeDumpManual", 0, VF_NULL, "Controls whether all functions are dumped in a profile log.");
	m_pStatoscopeDumpAllCVar = REGISTER_INT("e_StatoscopeDumpAll", 0, VF_NULL, "Controls whether all functions are dumped in a profile log.");
	m_pStatoscopeDataGroupsCVar = REGISTER_INT("e_StatoscopeDataGroups", m_activeDataGroupMask, VF_BITFIELD, GetDataGroupsCVarHelpString());
	m_pStatoscopeScreenshotCapturePeriodCVar = REGISTER_FLOAT("e_StatoscopeScreenshotCapturePeriod", -1.0f, VF_NULL, "How many seconds between Statoscope screenshot captures (-1 to disable).");
	m_pStatoscopeFilenameUseBuildInfoCVar = REGISTER_INT("e_StatoscopeFilenameUseBuildInfo", 1, VF_NULL, "Set to include the platform and build number in the log filename.");
	m_pStatoscopeFilenameUseMapCVar = REGISTER_INT("e_StatoscopeFilenameUseMap", 0, VF_NULL, "Set to include the map name in the log filename.");
	m_pStatoscopeFilenameUseTagCvar = REGISTER_STRING("e_StatoscopeFilenameUseTag", "", VF_NULL, "Set to include tag in the log file name.");
	m_pStatoscopeFilenameUseTimeCVar = REGISTER_INT("e_StatoscopeFilenameUseTime", 0, VF_NULL, "Set to include the time and date in the log filename.");
	m_pStatoscopeMinFuncLengthMsCVar = REGISTER_FLOAT("e_StatoscopeMinFuncLengthMs", 0.01f, VF_NULL, "Min func duration (ms) to be logged by statoscope.");
	m_pStatoscopeMaxNumFuncsPerFrameCVar = REGISTER_INT("e_StatoscopeMaxNumFuncsPerFrame", 40, VF_NULL, "Max number of funcs to log per frame.");
	m_pStatoscopeCreateLogFilePerLevelCVar = REGISTER_INT("e_StatoscopeCreateLogFilePerLevel", 0, VF_NULL, "Create a new perflog file per level.");
	m_pStatoscopeSocketAddress = REGISTER_STRING("e_StatoscopeSocketAddress", "", VF_NULL, "Set to dump log via a server at the specified address.");
	m_pStatoscopeAllowFPSOverrideCVar = REGISTER_INT("e_StatoscopeAllowFpsOverride", 1, VF_NULL, "Allow overriding of cvars in release for fps captures (MP only).");
	m_pTimerSmoothingCVar = NULL;
	m_pGameRulesCVar = NULL;

	m_logNum=1;
	m_bLogFileCreated = false;
	m_bCreateNewLog = false;
	m_bHaveOutputModuleInformation = false;

	m_socketWriter = NULL;
	m_socketBufferEnd = 0;

	REGISTER_COMMAND("e_StatoscopeAddUserMarker", &ConsoleAddUserMarker, 0, "Add a user marker to the perf stat logging for this frame");

	gEnv->pSystem->GetISystemEventDispatcher()->RegisterListener(this);

	CryCreateDirectory("statoscope", NULL);
}

CStatoscope::~CStatoscope()
{
	if (m_socketWriter)
	{
		m_socketWriter->Close();
		delete m_socketWriter;
	}

	gEnv->pSystem->GetISystemEventDispatcher()->RemoveListener(this);
	gEnv->pRenderer->UnRegisterCaptureFrame(this);
	delete[] m_pScreenShotBuffer;
	m_pScreenShotBuffer = NULL;



	m_pScreenShotRSXOffset = 0;
}

static char* Base64Encode(const uint8 *buffer, int len)
{
	static const char base64Dict[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
																			 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
																			 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
																			 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
	int b64Len = ((len + 2) / 3) * 4;
	char *b64Buf = new char[b64Len + 1];
	int byteCount = 0;
	int cycle = 0;

	for (int i=0; i<b64Len; i++)
	{
		uint8 val = 0;
		
		switch (cycle)
		{
		case 0:
			val = buffer[byteCount] >> 2;
			break;
		case 1:
			val = (buffer[byteCount++] & 0x3) << 4;
			val |= buffer[byteCount] >> 4;
			break;
		case 2:
			val = (buffer[byteCount++] & 0xf) << 2;
			val |= buffer[byteCount] >> 6;
			break;
		case 3:
			val = buffer[byteCount++] & 0x3f;
			break;
		}

		cycle = (cycle + 1) & 3;

		b64Buf[i] = base64Dict[val];
	}

	b64Buf[b64Len] = '\0';

	return b64Buf;
}

void CStatoscope::Tick()
{
	ScopedSwitchToGlobalHeap useGlobalHeap;
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_SYSTEM);

	float dumpPeriod = m_pStatoscopeDumpPeriodCVar->GetFVal();
	bool automaticDump = dumpPeriod > 0.0f;
	bool manualDump = m_pStatoscopeDumpManualCVar->GetIVal() != 0;

	if (!manualDump && dumpPeriod == 0.0f && m_perfStatFrameRecords.size() > 0)
	{
		CryLogAlways("Flushing Statoscope log\n");
		DumpFrameRecords(false);
	}
	else if (automaticDump || manualDump)
	{
		SetIsRunning(true);

		if (!m_pTimerSmoothingCVar)
		{
			m_pTimerSmoothingCVar = gEnv->pConsole->GetCVar("t_Smoothing");
		}

		if (m_pTimerSmoothingCVar)
		{
			m_pTimerSmoothingCVar->Set(0);
		}

		uint32 currentActiveDataGroupMask = (uint32)m_pStatoscopeDataGroupsCVar->GetIVal();
		bool dataGroupMaskChanged = currentActiveDataGroupMask != m_activeDataGroupMask;

		float currentTime = gEnv->pTimer->GetFrameStartTime(ITimer::ETIMER_UI).GetSeconds();

		if (m_groupMaskInitialized && 
			(((m_lastDumpTime == 0.0f || currentTime >= m_lastDumpTime + dumpPeriod) && automaticDump) || dataGroupMaskChanged) )
		{
			DumpFrameRecords(false);
		}

		if (dataGroupMaskChanged)
		{
			bool qWasActive = (m_activeDataGroupMask & AlphaBit('q')) != 0;
			bool qIsNowActive = (currentActiveDataGroupMask & AlphaBit('q')) != 0;

			if (qWasActive && !qIsNowActive)
			{
				SPerfCountersDG::ShutdownPM();
			}

			if (qIsNowActive && !qWasActive)
			{
				SPerfCountersDG::InitPM();
			}

			m_activeDataGroupMask = currentActiveDataGroupMask;

			SetDataGroups();
			if(m_groupMaskInitialized)
				DumpFrameRecords(true);
			else
				m_groupMaskInitialized = true;
		}

		AddFrameRecord();
	}
	else
	{
		SetIsRunning(false);
	}
}

class CCompareFrameProfilersSelfTime
{
public:
	bool operator() (const CFrameProfiler *p1, const CFrameProfiler *p2)
	{
		return p1->m_selfTime > p2->m_selfTime;
	}
};


void CStatoscope::SetCurrentTexturePoolAllocations(std::vector<STexturePoolAllocation*> *allocations)
{
	ScopedSwitchToGlobalHeap useGlobalHeap;
	uint32 numAllocations = allocations->size();
	
	m_texturePoolAllocationRecords.reserve(numAllocations);
	m_texturePoolAllocationRecords.resize(0);

	for (uint32 i=0; i<numAllocations; i++)
	{
		STexturePoolAllocation * pAllocations = (*allocations)[i];
		SPerfTexturePoolAllocationRecord textureAllocationRecord;
		textureAllocationRecord.m_size = pAllocations->size;
		textureAllocationRecord.m_path = pAllocations->texturepath;
		m_texturePoolAllocationRecords.push_back(textureAllocationRecord);
	}
}

void CStatoscope::SetCurrentProfilerRecords(const std::vector<CFrameProfiler*> *profilers)
{
	ScopedSwitchToGlobalHeap useGlobalHeap;

	// we want to avoid reallocation of m_perfStatDumpProfilers
	// even if numProfilers is quite large (in the thousands), it'll only be tens of KB
	uint32 numProfilers = profilers->size();
	m_perfStatDumpProfilers.reserve(MAX(numProfilers, m_perfStatDumpProfilers.size()));
	m_perfStatDumpProfilers.resize(0);
	
	float minFuncTime = m_pStatoscopeMinFuncLengthMsCVar->GetFVal();

	int64 smallFuncs = 0;
	uint32 smallFuncsCount = 0;

	for (uint32 i=0; i<numProfilers; i++)
	{
    CFrameProfiler *pProfiler = (*profilers)[i];

		// ignore really quick functions or ones what weren't called
		if (gEnv->pTimer->TicksToSeconds(pProfiler->m_selfTime)*1000 > minFuncTime)
		{
			m_perfStatDumpProfilers.push_back(pProfiler);
		}
		else
		{
			smallFuncs += pProfiler->m_selfTime;
			smallFuncsCount++;
		}
	}

	std::sort(m_perfStatDumpProfilers.begin(), m_perfStatDumpProfilers.end(), CCompareFrameProfilersSelfTime());

	bool bDumpAll = false;

	if (m_pStatoscopeDumpAllCVar->GetIVal())
	{
			bDumpAll = true;
	}

	if (!bDumpAll)
	{
		uint32 maxNumFuncs = (uint32)m_pStatoscopeMaxNumFuncsPerFrameCVar->GetIVal();
		// limit the number being recorded
		m_perfStatDumpProfilers.resize(MIN(m_perfStatDumpProfilers.size(), maxNumFuncs));
	}

	uint32 numDumpProfilers = m_perfStatDumpProfilers.size();
	m_frameProfilerRecords.reserve(numDumpProfilers);
	m_frameProfilerRecords.resize(0);

	for (uint32 i=0; i<numDumpProfilers; i++)
	{
    CFrameProfiler *pProfiler = m_perfStatDumpProfilers[i];
		SPerfStatFrameProfilerRecord profilerRecord;

		profilerRecord.m_pProfiler = pProfiler;
		profilerRecord.m_count = pProfiler->m_count;
		profilerRecord.m_selfTime = gEnv->pTimer->TicksToSeconds(pProfiler->m_selfTime)*1000;

		m_frameProfilerRecords.push_back(profilerRecord);
	}

	if (bDumpAll)
	{
		SPerfStatFrameProfilerRecord profilerRecord;

		profilerRecord.m_pProfiler = NULL;
		profilerRecord.m_count = smallFuncsCount;
		profilerRecord.m_selfTime = gEnv->pTimer->TicksToSeconds(smallFuncs)*1000;

		m_frameProfilerRecords.push_back(profilerRecord);
	}
}

void CStatoscope::PrepareScreenShot()
{
	if( !m_pScreenShotBuffer )
	{
		const int SCREENSHOT_BIT_DEPTH = 4;
		static int shrunkenWidth = gEnv->pRenderer->GetWidth() / SCREENSHOT_SCALING_FACTOR;
		static int shrunkenHeight = gEnv->pRenderer->GetHeight() / SCREENSHOT_SCALING_FACTOR;













		m_pScreenShotBuffer = new uint8[ shrunkenWidth * shrunkenHeight * SCREENSHOT_BIT_DEPTH ];	

		memset( (void*)m_pScreenShotBuffer, 0, ( sizeof( uint8 ) * shrunkenWidth * shrunkenHeight * SCREENSHOT_BIT_DEPTH ) );
		gEnv->pRenderer->RegisterCaptureFrame( this );
	}

}

uint8* CStatoscope::ProcessScreenShot()
{
	//Reserved bytes in buffer indicate size and scale
	enum { SCREENSHOT_SCALED_WIDTH, SCREENSHOT_SCALED_HEIGHT, SCREENSHOT_MULTIPLIER };
	uint8 *pScreenshotBuf = NULL;

	if( m_ScreenShotState == eSSCS_DataReceived )
	{
		const int SCREENSHOT_BIT_DEPTH = 4;
		const int SCREENSHOT_TARGET_BIT_DEPTH = 3;
		static int width = gEnv->pRenderer->GetWidth();
		static int shrunkenWidth =  width / SCREENSHOT_SCALING_FACTOR;
		static int shrunkenHeight = gEnv->pRenderer->GetHeight() / SCREENSHOT_SCALING_FACTOR;

		pScreenshotBuf = new uint8[ 3 + ( shrunkenWidth * shrunkenHeight * SCREENSHOT_TARGET_BIT_DEPTH ) ];

		if( pScreenshotBuf )
		{
			pScreenshotBuf[ SCREENSHOT_MULTIPLIER ] = ( uint8 )( ( max( shrunkenWidth, shrunkenHeight ) + UCHAR_MAX ) / UCHAR_MAX );	//Scaling factor
			pScreenshotBuf[ SCREENSHOT_SCALED_WIDTH ] = ( uint8 )( shrunkenWidth / pScreenshotBuf[ SCREENSHOT_MULTIPLIER ] );
			pScreenshotBuf[ SCREENSHOT_SCALED_HEIGHT ] = ( uint8 )( shrunkenHeight / pScreenshotBuf[ SCREENSHOT_MULTIPLIER ] );
			int iSrcPixel = 0;
			int iDstPixel = 3;
			for( int y = 0; y < shrunkenHeight; ++y )
			{
				for( int x = 0; x < width*SCREENSHOT_BIT_DEPTH; x += SCREENSHOT_SCALING_FACTOR*SCREENSHOT_BIT_DEPTH )
			{
					pScreenshotBuf[ iDstPixel ] = m_pScreenShotBuffer[ iSrcPixel+x ]; // blue
					pScreenshotBuf[ iDstPixel+1 ] = m_pScreenShotBuffer[ iSrcPixel+x+1 ];; // green
					pScreenshotBuf[ iDstPixel+2 ] = m_pScreenShotBuffer[ iSrcPixel+x+2 ]; // red

				iDstPixel += SCREENSHOT_TARGET_BIT_DEPTH;
			}
				iSrcPixel += (width*SCREENSHOT_SCALING_FACTOR*SCREENSHOT_BIT_DEPTH);
			}
			m_ScreenShotState = eSSCS_Idle;
		}
	}

	return pScreenshotBuf;
}

void CStatoscope::AddFrameRecord()
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_SYSTEM);

	float currentTime = gEnv->pTimer->GetFrameStartTime(ITimer::ETIMER_UI).GetSeconds();

	SPerfStatFrameRecord fr;
	fr.m_frameTimeInS = currentTime;
	

	//Screen shot in progress, attempt to process
	if(m_ScreenShotState == eSSCS_DataReceived)
	{
		fr.m_pScreenshotBuf = ProcessScreenShot();
	}

	//auto screen shot logic
	float screenshotCapturePeriod = m_pStatoscopeScreenshotCapturePeriodCVar->GetFVal();

	if ( (m_ScreenShotState == eSSCS_Idle) && (screenshotCapturePeriod >= 0.0f) )
	{
		if( currentTime >= m_screenshotLastCaptureTime + screenshotCapturePeriod )
		{
			//Tell the Render thread to dump the mini screenshot to main memory
			//Then wait for the callback to tell us the data is ready
			RequestScreenShot();
			m_screenshotLastCaptureTime = currentTime;
		}
	}

	fr.m_frameProfilerRecords.swap(m_frameProfilerRecords);
	fr.m_texturePoolAllocationRecords.swap(m_texturePoolAllocationRecords);
	m_userMarkers.swap(fr.m_userMarkers);
	m_callstacks.swap(fr.m_callstacks);

	for (uint32 i=0; i<m_activeDataGroups.size(); i++)
	{
		SDataGroup *dataGroup = m_activeDataGroups[i];
		dataGroup->AddDataToFrameRecord(fr);
	}

	m_perfStatFrameRecords.push_back(fr);
}

void CStatoscope::DumpFrameRecords(bool bOutputFormat)
{
	ScopedSwitchToGlobalHeap useGlobalHeap;

	// can't use the helper struct because statoscope also needs to work in release mode
	uint32 nThreadId = CryGetCurrentThreadId();
	bool bOldDisableRuntimeAccess = gEnv->pCryPak ? gEnv->pCryPak->DisableRuntimeFileAccess(false, nThreadId) : false;

	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_SYSTEM);
	const char* modeStr = "at";

	// first time we have openStatoscope file, open for write

	if(!m_bLogFileCreated || m_bCreateNewLog)
	{
		modeStr = "wt";
		bOutputFormat = true;

		if(m_logFilename.empty())
		{
			SetLogFilename();
		}

		const char* socketAddress = m_pStatoscopeSocketAddress->GetString();
		if (socketAddress && socketAddress[0])
		{
			if (m_socketWriter)
			{
				m_socketWriter->Close();
				delete m_socketWriter;
			}

			m_socketWriter = new ReplaySocketWriter(socketAddress);
			m_socketWriter->Open();
			m_socketBuffer.resize(65536);
			m_socketBufferEnd = 0;
		}

		m_bCreateNewLog = false;
	}

	FILE *pLogFile = NULL;

	if (!m_socketWriter)
	{



		pLogFile = fxopen(m_logFilename.c_str(), modeStr);

	}

	LineWriterFunc lineWriter = NULL;
	void* lineWriterInst = NULL;

	if (pLogFile)
		{
		CryLogAlways("Dumping Statoscope log to '%s'\n", m_logFilename.c_str());
		lineWriter = OutputLine;
		lineWriterInst = pLogFile;
		}
	else if (m_socketWriter)
		{
		CryLogAlways("Dumping Statoscope log to socket\n");
		lineWriter = &CStatoscope::WriteLineSocket;
		lineWriterInst = this;
	}

	if (lineWriterInst && lineWriter)
	{
		if (m_activeDataGroupMask & AlphaBit('k'))
		{
			OutputLoadedModuleInformation(lineWriter, lineWriterInst);
		}

		if (bOutputFormat)
		{
			lineWriter(lineWriterInst, "<format>\n");
			lineWriter(lineWriterInst, "['/' (float frameTimeInS) (b64texture screenshot)]\n");

			for (uint32 i=0; i<m_activeDataGroups.size(); i++)
			{
				SDataGroup *dataGroup = m_activeDataGroups[i];
				lineWriter(lineWriterInst, "%s\n", dataGroup->m_format);
			}

			lineWriter(lineWriterInst, "<\\format>\n");
		}

		for (uint32 i=0; i<m_perfStatFrameRecords.size(); i++)
		{
			SPerfStatFrameRecord &fr = m_perfStatFrameRecords[i];

			char *encodedScreenshotBuf = fr.m_pScreenshotBuf ? Base64Encode(fr.m_pScreenshotBuf, 3 + (fr.m_pScreenshotBuf[0] * fr.m_pScreenshotBuf[1] * 3)) : NULL;

			lineWriter(lineWriterInst, "[%f %s]\n", fr.m_frameTimeInS, encodedScreenshotBuf ? encodedScreenshotBuf : "");

			delete[] encodedScreenshotBuf;
			delete[] fr.m_pScreenshotBuf;

			for (uint32 dg=0; dg<m_activeDataGroups.size(); dg++)
			{
				SDataGroup *dataGroup = m_activeDataGroups[dg];

				for (uint32 ds=0; ds<dataGroup->GetNumDataSets(fr); ds++)
				{
					lineWriter(lineWriterInst, "[");

					for (uint32 de=0; de<dataGroup->m_numDataElements; de++)
					{
						const SDataElement &dataElement = fr.GetNextValue();

						if (de != 0)
						{
							lineWriter(lineWriterInst, " ");
						}

						switch (dataElement.m_dataType)
						{
						case DATATYPE_INT:
							lineWriter(lineWriterInst, "%d", dataElement.m_i);
							break;

						case DATATYPE_FLOAT:
							lineWriter(lineWriterInst, "%f", dataElement.m_f);
							break;

						case DATATYPE_STRING:
							lineWriter(lineWriterInst, "'%s'", dataElement.m_s);
							break;

						case DATATYPE_PATH:
							lineWriter(lineWriterInst, "'%s'", dataGroup->GetPath(dataElement.m_p).c_str());
							break;
						}
					}

					lineWriter(lineWriterInst, "]");
				}

				lineWriter(lineWriterInst, "\n");
			}
		}

		m_bLogFileCreated = true;
	}

	if (pLogFile)
		{
		fclose(pLogFile);
		}

	if (m_socketWriter)
	{
		if (m_socketBufferEnd > 0)
		{
			m_socketWriter->Write(&m_socketBuffer[0], 1, m_socketBufferEnd);
			m_socketBufferEnd = 0;
		}
		m_socketWriter->Flush();
	}

	m_lastDumpTime = gEnv->pTimer->GetFrameStartTime(ITimer::ETIMER_UI).GetSeconds();
	m_perfStatFrameRecords.clear();

	if(gEnv && gEnv->pCryPak)
	{
		gEnv->pCryPak->DisableRuntimeFileAccess(bOldDisableRuntimeAccess, nThreadId);
	}
}

bool CStatoscope::NeedsGPUTimers()
{
	// it's a shame to have to do it like this, but I don't have time right now to implement it in a better way :(
	return (m_activeDataGroupMask & AlphaBit('i')) != 0;
}

void CStatoscope::SetLogFilename()
{
	m_logFilename = "statoscope/perf";







	if (m_pStatoscopeFilenameUseBuildInfoCVar->GetIVal() > 0)
	{




#if defined(WIN64)
		m_logFilename += "_WIN64";
#elif defined(WIN32)	// make sure this stays below the WIN64 test as WIN32 is also defined for 64-bit compiles
		m_logFilename += "_WIN32";
#else
		m_logFilename += "_UNKNOWNPLAT";
#endif

		const SFileVersion &ver = gEnv->pSystem->GetFileVersion();
		char versionString[64];
		sprintf_s(versionString, sizeof(versionString), "_%d_%d_%d_%d", ver.v[3], ver.v[2], ver.v[1], ver.v[0]);

		m_logFilename += versionString;
	}

	if ( strcmp(m_pStatoscopeFilenameUseTagCvar->GetString(),"") != 0)
	{
		m_logFilename += "_";
		m_logFilename += m_pStatoscopeFilenameUseTagCvar->GetString();
	}

	if (m_pStatoscopeFilenameUseMapCVar->GetIVal() > 0)
	{
		const char* mapName = NULL;

		//If we don't have a last map loaded, try to look up the current one now
		if (m_currentMap.empty())
		{
			if (gEnv->pGame)
			{
				mapName = gEnv->pGame->GetIGameFramework()->GetLevelName();
			}
		}
		//If we tracked the last map loaded then use it here
		else
		{
			mapName = m_currentMap.c_str();
		}

		if (mapName)
		{
			const char *nameNoDir = strrchr(mapName, '/');

			// strip directory for now
			if (nameNoDir)
			{
				mapName = nameNoDir + 1;
			}

			string mapNameTrucated(mapName);

			m_logFilename += "_";
			int mapNameLenLimit = 32;
			if (m_pStatoscopeFilenameUseBuildInfoCVar->GetIVal() > 0)
			{
				mapNameLenLimit = 12;
			}
			m_logFilename += mapNameTrucated.Left(mapNameLenLimit);
		}
	}

	if (m_pStatoscopeFilenameUseTimeCVar->GetIVal() > 0)
	{
		time_t curTime;
		time(&curTime);
		const struct tm *lt = localtime(&curTime);

		char name[MAX_PATH];
		strftime(name, sizeof(name) / sizeof(name[0]), "_%Y%m%d_%H%M%S", lt);

		m_logFilename += name;
	}

	//ensure unique log name
	if(m_pStatoscopeCreateLogFilePerLevelCVar->GetIVal())
	{
		char logNumBuf[10];
		sprintf_s(logNumBuf, 10, "_%d", m_logNum++);
		m_logFilename += logNumBuf;
	}
	
	m_bCreateNewLog = true;
	m_bHaveOutputModuleInformation = false;

	m_logFilename += ".log";








}

void CStatoscope::SetDataGroups()
{
	m_activeDataGroups.clear();

	for (uint32 i=0; i<m_allDataGroups.size(); i++)
	{
		SDataGroup *dg = m_allDataGroups[i];
		if ((m_activeDataGroupMask == 1) || (AlphaBit(dg->m_id) & m_activeDataGroupMask))
		{
			m_activeDataGroups.push_back(dg);
		}
	}
}

void CStatoscope::OutputLoadedModuleInformation(LineWriterFunc lineWriter, void* lineWriterInst)
{
	if (m_bHaveOutputModuleInformation)
	{
		return;
	}































	m_bHaveOutputModuleInformation = true;
}

void CStatoscope::StoreCallstack(const char* tag, void** callstackAddresses, uint32 callstackLength)
{
	ScopedSwitchToGlobalHeap useGlobalHeap;

	CryMT::vector<SCallstack>::AutoLock lock(m_callstacks.get_lock());
	SCallstack callstack(callstackAddresses, callstackLength, tag);
	m_callstacks.push_back(SCallstack());
	m_callstacks.back().swap(callstack);
}

int CStatoscope::WriteLineSocket(void* vinst, const char* format, ...)
{
	CStatoscope* statoscope = reinterpret_cast<CStatoscope*>(vinst);
	ReplaySocketWriter* sock = statoscope->m_socketWriter;

	va_list args;
	va_start(args, format);
	size_t capacity = statoscope->m_socketBuffer.size() - statoscope->m_socketBufferEnd;
	int ret = vsnprintf(&statoscope->m_socketBuffer[statoscope->m_socketBufferEnd], capacity, format, args);
	if (ret < 0 || ret >= static_cast<int>(capacity))
	{
		sock->Write(&statoscope->m_socketBuffer[0], 1, statoscope->m_socketBufferEnd);
		statoscope->m_socketBufferEnd = 0;
		ret = vsnprintf(&statoscope->m_socketBuffer[0], statoscope->m_socketBuffer.size(), format, args);
	}
	va_end(args);

	if (ret > 0)
	{
		statoscope->m_socketBufferEnd += (size_t) ret;
	}

	return ret;
}

void CStatoscope::AddUserMarker(const char *name)
{
	if (!IsRunning())
	{
		return;
	}

	ScopedSwitchToGlobalHeap useGlobalHeap;
	m_userMarkers.push_back(name);
}

void CStatoscope::LogCallstack(const char *tag)
{
	if (!IsRunning())
	{
		return;
	}

	uint32 callstackLength = 128;
	void* callstackAddresses[128];

	CSystem::debug_GetCallStackRaw(callstackAddresses, callstackLength);
	StoreCallstack(tag, callstackAddresses, callstackLength);
}

void CStatoscope::LogCallstackFormat(const char *tagFormat, ...)
{
	if (!IsRunning())
	{
		return;
	}

	va_list args;
	va_start(args, tagFormat);
	char tag[256];
	vsprintf_s(tag, sizeof(tag), tagFormat, args);
	va_end(args);

	uint32 callstackLength = 128;
	void* callstackAddresses[128];

	CSystem::debug_GetCallStackRaw(callstackAddresses, callstackLength);
	StoreCallstack(tag, callstackAddresses, callstackLength);
}

void CStatoscope::ConsoleAddUserMarker(IConsoleCmdArgs* pParams)
{
	if (pParams->GetArgCount() == 2)
	{
		gEnv->pStatoscope->AddUserMarker(pParams->GetArg(1));
	}
	else
	{
		CryLogAlways("Invalid use of e_StatoscopeAddUserMarker. Expecting 1 argument, not %d.\n", pParams->GetArgCount()-1);
	}
}

void CStatoscope::OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam)
{
	switch (event)
	{
	case ESYSTEM_EVENT_LEVEL_PRECACHE_END:
		{
				if(!m_pGameRulesCVar)
				{
					m_pGameRulesCVar = gEnv->pConsole->GetCVar("sv_gamerules");
				}

				const char *mapName = NULL;

				if (gEnv->pGame && gEnv->pGame->GetIGameFramework())
				{
					mapName = gEnv->pGame->GetIGameFramework()->GetLevelName();
				}

				if (!mapName)
				{
					mapName = "unknown_map";
				}
				else
				{
					ScopedSwitchToGlobalHeap globalHeap;
					m_currentMap = mapName;
				}

				string userMarker = "level_start ";
				userMarker += mapName;

				if(m_pGameRulesCVar)
				{
					userMarker += " ";
					userMarker += m_pGameRulesCVar->GetString();
				}

				AddUserMarker(userMarker.c_str());

				if(m_pStatoscopeCreateLogFilePerLevelCVar->GetIVal())
				{
					//force new log file
					SetLogFilename();
				}
		}
		break;
	case ESYSTEM_EVENT_LEVEL_UNLOAD:
		{
			AddUserMarker("level_end");
			if (m_pStatoscopeDumpManualCVar->GetIVal() < 0 || m_pStatoscopeDumpPeriodCVar->GetFVal() > 0.0f)
			{
				AddFrameRecord();
				DumpFrameRecords(false);
			}
		}
		break;
	}
}

//Screenshot capturing
unsigned char *CStatoscope::OnNeedFrameData(void)
{
	//The renderer will only perform the screen grab if we supply a pointer from this callback. Since we only want one intermittently return null most of the time
	if( m_ScreenShotState == eSSCS_AwaitingBufferRequest || m_ScreenShotState == eSSCS_AwaitingCapture )
	{
		m_ScreenShotState = eSSCS_AwaitingCapture;
		return m_pScreenShotBuffer;
	}
	return NULL;
}

uint32 CStatoscope::OnNeedFrameDataRSXOffset(void)
{
	//The renderer will only perform the screen grab if we supply a pointer from this callback. Since we only want one intermittently return null most of the time
	if( m_ScreenShotState == eSSCS_AwaitingBufferRequest )
	{
		m_ScreenShotState = eSSCS_AwaitingCapture;
		return m_pScreenShotRSXOffset;
	}
	return NULL;
}

void CStatoscope::OnFrameCaptured(void)
{
	//The renderer has finished copying the screenshot into the buffer. Change state so we write the shot out.
	m_ScreenShotState = eSSCS_DataReceived;
}

int CStatoscope::OnGetFrameWidth(void)
{
	return gEnv->pRenderer->GetWidth();
}

int CStatoscope::OnGetFrameHeight(void)
{
	return gEnv->pRenderer->GetHeight();
}

int CStatoscope::OnGetCaptureFlags(void)
{
	int flags = 0;






	return flags;
}

int CStatoscope::OnCaptureFrameBegin(void)
{
	//Called at the beginning of the rendering pass, the flags returned determine if the screenshot render target gets written to.
	//For performance reasons we do this infrequently.
	int flags = 0;
	if( m_ScreenShotState == eSSCS_RequestCapture )
	{
		flags |= eCFF_CaptureThisFrame;
		m_ScreenShotState = eSSCS_AwaitingBufferRequest;	//Frame initialised. Wait for the buffer to be requested
	}

	return flags;
}

void CStatoscope::SetupFPSCaptureCVars()
{
	if(m_pStatoscopeAllowFPSOverrideCVar && m_pStatoscopeAllowFPSOverrideCVar->GetIVal()!=0)
	{
		if(m_pStatoscopeDumpPeriodCVar)
		{
			m_pStatoscopeDumpPeriodCVar->Set(20.f);
		}
		if(m_pStatoscopeDataGroupsCVar)
		{
			m_pStatoscopeDataGroupsCVar->Set("fgmut");
		}
		if(m_pStatoscopeFilenameUseBuildInfoCVar)
		{
			m_pStatoscopeFilenameUseBuildInfoCVar->Set("0");
		}
		if (m_pStatoscopeFilenameUseMapCVar)
		{
			m_pStatoscopeFilenameUseMapCVar->Set("1");
		}
		if (m_pStatoscopeCreateLogFilePerLevelCVar)
		{
			m_pStatoscopeCreateLogFilePerLevelCVar->Set("1");
		}

		ICVar *pCVar = gEnv->pConsole->GetCVar("e_StatoscopeScreenCapWhenGPULimited");
		if(pCVar)
		{
			pCVar->Set(1);
		}
	}
}

bool CStatoscope::RequestScreenShot()
{
	if(m_ScreenShotState == eSSCS_Idle)
	{
		m_ScreenShotState = eSSCS_RequestCapture;
		PrepareScreenShot();
		return true;
	}

	return false;
}

#endif // ENABLE_STATOSCOPE
